#ifndef BMD_GOBJS_H
#define BMD_GOBJS_H

#include <cmath>
#include <vector>
#include <iostream>
#include "FreeImage.h"

//the following 3 included for rand(), srand()
#include "math.h"
#include "stdlib.h"
#include "time.h"

#include <vm/actor.hpp>
#include <vm/vm.hpp>

using namespace std;
using namespace BMD;

//enum OrderType {otVOID = 0, otMOVE = 1, otBOMB = 2}; //add use options
//enum BombOrderType {otVOID = 0, otDETONATE = 1};
//enum StageDirection {dVOID = 0, dNORTH = -2, dEAST = 1, dSOUTH = 2, dWEST = -1}; //movement directions on the stage
static const int dVOID = 0;
static const int dNORTH = -2;
static const int dEAST = 1;
static const int dSOUTH = 2;
static const int dWEST = -1;

int to_vm_direction(int direction);
int from_vm_direction(int vm_dir);

static const int tUNKOWN = -1;
static const int tEMPTY = 0;
static const int tBRICK = 1;
static const int tBLOCK = 2;
static const int tEXPLOSION = 3;
static const int tEXPLOSION_BRICK = 4;

int to_vm_tile(int tile);

static const float POWERUP_ODDS = 0.2;

static const int pVOID = -1;
static const int pBOMBS = 0;
static const int pSPEED = 1;
static const int pPOWER = 2;
static const int pKICKING = 3;
static const int pDETONATION = 4;

static const int pOVER_MAX_INDEX = 5; //whatever the highest powerup index is + 1

int to_vm_powerup(int power_up);
int from_vm_powerup(int vm_power_up);

enum BomberStatus {bDEAD = -1, bALIVE = 0};


static float NEAR_CRITERION = (float)0.3; //% of duration left before a bomb is considered 'near' an explosion.

//some of these are temporary
static unsigned int BOMBER_STEPS = 6;	//6 steps of motion (time frames) before entering a new tile
static unsigned int BOMB_STEPS = 2;		//bombs are twice as fast

static unsigned int MAX_ALLOWED_BOMBS = 9999;
static unsigned int DEFAULT_BOMBS = 2;
static unsigned int MIN_MOVE_STEPS = 2;
static bool			DEFAULT_KICKING = false;
static bool			DEFAULT_DETONATION = false;

static unsigned int DEFAULT_BOMB_DURATION = 65;
static unsigned int DEFAULT_BOMB_POWER = 2;

static unsigned int STARTING_SCORE = 0;
static unsigned int TIMER_DELAY = 200;


static unsigned int DEFAULT_EXPLOSION_DURATION = 6;

static float DEFAULT_BRICK_DENSITY = (float)0.7;

static const char * fname_PLAYER_DATA = "playerdat.txt";


//random integer from 0 to max - 1 function
static int rint(int max)
{
	if(max > 0)
		return rand() % max;
	return 0;
}

//returns true with probability ~p
static bool prob(float p)
{
  /*
	float temp = rand() + 1000.0; //added 1000 to increase "precision"
	return (rint(temp) < p * temp);
  */
  return (double)rand() / double(RAND_MAX) < p;
}

static int to_vm_movespeed(int step)
{ return 2*step + 1; }

struct Coordinates
{
	unsigned int x;
	unsigned int y;

	Coordinates(){x = y = 0;};
	inline Coordinates(const unsigned int & i, const unsigned int & j){x = i; y = j;}
  
  Value to_value() const;
};



/*
- Movement between two tiles is controlled by the MovementState.
- This object is queried, modified and updated in order to simulate 
the transition between two tiles.

*/
class MovementState
{
private:
	int direction;
	int step;		//what stage of the movement between two locations is the player in?
	int total_steps; //total number of steps needed to enter the next tile.

	void movement(Coordinates & coords)
	{
		if(direction > 0)
		{
			if(direction == dSOUTH)
				coords.y++;
			else
				coords.x++; //east is also positive
		}
		else
		{
			if(direction == dNORTH)
				coords.y--;
			else
				coords.x--; //west is negative
		}
	}

public:

	void set_steps(int in_steps)
	{
		total_steps = in_steps;
		if(step > total_steps)
			step--;
		else if(step < total_steps) //this should always be true
			step++;
	}

	void init(unsigned int in_t_steps)
	{
		direction = dVOID;
		step = 0;
		total_steps = in_t_steps;
	}

	void reset(unsigned int in_direction)
	{
		direction = in_direction;
		step = 0;
	}

	int get_direction() const
	{return direction;}
	// CHANGE
	Coordinates reserved(const Coordinates & coords)
	{
		Coordinates ret = coords;
		if(direction * step > 0)
			return dest(ret);
		else if(direction * step < 0)
			return source(ret);
		return ret; //this should not be reachable

	}

	Coordinates dest(const Coordinates & coords)
	{
		Coordinates ret = coords;
		movement(ret);
		return ret;
	}

	//finds the previous Coordinates that would arive at these, opposite of dest
	Coordinates source(const Coordinates & coords)
	{
		Coordinates ret = coords;
		direction = -1 * direction; //reverse direction
		movement(ret); direction = -1 * direction; //find ret, reset direction
		return ret;
		
	}

	bool tick()
	{
		if(direction > 0)
		{	
			if(step >= total_steps)
				return true;
			step++;}
		else if(direction < 0)
		{	if(step <= -total_steps)
				return true;
			step--;}
		
		return false; //no movement occured
	}

	//the directions are set up so that the signs are opposite, or the current is 0
	inline bool allowed_redirect(int new_direction)
	{return !step || !direction || (direction + new_direction == 0);} // CHANGE

	bool change_direction(int new_direction)
	{
		if(allowed_redirect(new_direction))
		{	direction = new_direction;
			return true; //successful change
		}
		return false; //succssful change
	}



	//if the player's "step" variable is zero, the player is at the center of a tile
	inline bool is_displaced() const {return (step != 0);} //kept giving performance warning


	//once a tile change has occured, this needs to be called
	//
	// updates the MovementState, but also a bomber's coordinates (which are input)
	//
	void perform_tile_change()//Coordinates & in_coords)
	{
		step *= -1;
		//coords = in_coords;
	}

	//being in a certain step of motion, or moving in a direction implies one is moving
	inline bool moving(){return (step || direction);}

	//movement fraction used for rendering
	double move_fracX()
	{if(direction == dWEST || direction == dEAST)
			return ((double)step)/(2*total_steps + 1);
		return 0.0;}

	double move_fracY()
	{if(direction == dNORTH || direction == dSOUTH)
			return ((double)step)/(2*total_steps + 1);
		return 0.0;}
};


struct BomberOrders
{
	bool bomb_place;
	bool detonate;
	//other on use bools
	int move_direction;

	BomberOrders()
	{	bomb_place = detonate = false;
		move_direction = dVOID;}

        BomberOrders(Value v);
	void clear()
	{	bomb_place = detonate = false;
		move_direction = dVOID;}

	bool move_ordered()
	{return (move_direction != 0);}


};

struct BombOrders
{
	bool detonate;

	BombOrders()
	{	detonate = false;}
};

struct BomberProperties
{
	unsigned int max_bombs;
	unsigned int move_steps; //half the number of steps to move to next tile
	bool kicking;
	bool detonation;
	
	BomberProperties()
	{max_bombs = DEFAULT_BOMBS; move_steps = BOMBER_STEPS;
		kicking = DEFAULT_KICKING; detonation = DEFAULT_DETONATION;}
	BomberProperties(unsigned int in_max_bombs, unsigned int in_move_steps)
	{max_bombs = in_max_bombs; move_steps = in_move_steps;
		kicking = DEFAULT_KICKING; detonation = DEFAULT_DETONATION;}

	inline void attempt_inc_bombs()
	{ if(max_bombs < MAX_ALLOWED_BOMBS) max_bombs++;}

	inline bool attempt_inc_move()
	{ 
		if(move_steps > MIN_MOVE_STEPS) 
		{move_steps--; return true;}
		return false;
	}

	inline void give_kicking()
	{ kicking = true;}

	inline void give_detonation()
	{ detonation = true;}

};

struct BombProperties
{
	unsigned int duration;
	unsigned int power;
	unsigned int move_steps;

	BombProperties()
	{duration = DEFAULT_BOMB_DURATION; power = DEFAULT_BOMB_POWER; move_steps = BOMB_STEPS;}
	BombProperties(unsigned int in_duration, unsigned int in_power)
	{duration = in_duration; power = in_power;}

	//used only for power up acquisition at the moment
	void increase_power()
	{power++;}
};

struct ScoringProperties
{
	unsigned int kills;
	unsigned int deaths;
	unsigned int score;

	ScoringProperties()
	{
		kills = deaths = 0;
		score = STARTING_SCORE;
	}

	ScoringProperties(unsigned int in_k, unsigned int in_d, unsigned int in_s)
	{
		kills = in_k;
		deaths = in_d;
		score = in_s;
	}
};
struct ActiveProperties
{
	bool kills;
	bool deaths;
	bool score;

	ActiveProperties()
	{
		kills = deaths = score = false;
	}

	ActiveProperties(bool in_k, bool in_d, bool in_s)
	{
		kills = in_k;
		deaths = in_d;
		score = in_s;
	}
};


class Bomber
{

public:

	int id;
	Coordinates coords;
	MovementState mstate;
	BomberOrders orders;
	BomberProperties prop;
	BombProperties bomb_prop;
	BomberStatus status;  
	ScoringProperties score;
	unsigned int team_id;
	unsigned int nbombs;

  shared_ptr<Actor> actor;
	/*
	Bomber()
	{
		mstate.init()
	}
	*/

        Bomber(unsigned int in_id) : actor(new Actor())
	{
		team_id = 0;
		id = in_id;
		status = bALIVE;
		mstate.init(prop.move_steps);
		orders.clear();
		nbombs = 0;

		//cerr << "Testing Constructor used for Bomber." << endl;
	}

	inline int get_id(){return id;}
	inline unsigned int get_team_id(){return team_id;}
	inline void set_max_bombs(unsigned int in_max){prop.max_bombs = in_max;}
	bool bombs_available(){return nbombs < prop.max_bombs;}

	void kill(){status = bDEAD;}
	bool is_alive(){return status != bDEAD;}

	void gain_powerup(int ptype)
	{
		switch(ptype)
		{
			case pBOMBS:
				prop.attempt_inc_bombs();
				break;
			case pSPEED:
				if(prop.attempt_inc_move())
					mstate.set_steps(prop.move_steps);
				break;
			case pPOWER:
				bomb_prop.increase_power();
				break;
			case pKICKING:
				prop.give_kicking();
				break;
			case pDETONATION:
				prop.give_detonation();
				break;
                default:
                  break;
		}
	}

	//for testing
	void rezz()
	{status = bALIVE;}

        // convert the bomber to a value
        Value to_value() const;

        void call_main()
        {
          call_with_catch("main");
        }

        void call_init()
        {
          vector<Value> args;
          args.push_back(coords.to_value());
          call_with_catch("on_init", args);
        }

        // convert the bomber's status to a value
        Value status_value() const;

       // DONE: add method for when a bomb explodes
        void notify_explosion(int bomb_id)
        {
          // send a call
          vector<Value> args;
          args.push_back(Value(bomb_id));
          call_with_catch("on_bomb_detonate", args);
        }

        void notify_death()
        {
          call_with_catch("on_death");
        }
     
        void notify_teammate_death()
        {
          call_with_catch("on_teammate_death");
        }

        // calls the actor vm to get a move, and stores it
        // in the bomber orders
        void request_orders(Value input)
        {
          vector<Value> args;
          args.push_back(input);
          
          try
          {
            Value vm_orders = actor->call_function("on_move_request", args);
            orders = BomberOrders(vm_orders);
          }
          catch (runtime_error e)
          {
            cerr << "in bomber " << id << ": " << e.what() << endl;
            actor->get_vm()->reset();
            orders = BomberOrders();
          }
        }

private:
        void call_with_catch(string func_name)
        {
          vector<Value> args;
          call_with_catch(func_name, args);
        }
        void call_with_catch(string func_name, const vector<Value>& args)
        {
          try
          {
            actor->call_function(func_name, args);
          }
          catch (runtime_error e)
          {
            cerr << "in bomber " << id << ": " << e.what() << endl;
            actor->get_vm()->reset();
          }
        }
};

class Bomb
{	
	//unsigned int initial_duration;
	unsigned int remaining_duration;
	unsigned int id;
	unsigned int owner_id; //the ID of the bomber that placed this bomb

public:
	Coordinates coords;
	MovementState mstate;
	BombOrders orders;
	BombProperties prop;
	bool gone;
	//unsigned int team_id;

	Bomb()
	{gone = false; mstate.init(prop.move_steps);}

	//testing?
	Bomb(const unsigned int in_id)
	{
		gone = false;
		id = in_id;
		prop.duration = remaining_duration = 10000;
		mstate.init(prop.move_steps);
		cerr << "Testing Constructor used for Bomb." << endl;
	}

	void set(	const unsigned int & in_id, 
				const unsigned int & in_owner_id, 
				const BombProperties & in_prop)
	{
		gone = false;
		id = in_id;
		owner_id = in_owner_id;
		prop = in_prop;
		remaining_duration = prop.duration;
		mstate.init(prop.move_steps);
	}

	int get_id() const {return id;}
	int get_owner_id() const {return owner_id;}

	bool tick()
	{
		remaining_duration--;
		return (!remaining_duration); //if the 'duration' is zero, return true, signaling a detonation
	}

	bool near_explosion(){return (prop.duration * NEAR_CRITERION) >= (float)remaining_duration;}

        Value to_value(int bomber_id) const;
}; 

class PowerUp
{
public:
	int id;
	int power_type_index;
	Coordinates coords;

	//am i having powerups expire?
        Value to_value() const;
};


struct ExplosionDat
{
	Coordinates center;
	unsigned int dN, dS, dW, dE; //the distance north, south, west, east of the explosion

	ExplosionDat()
	{dN = dS = dW = dE = 0;}
};

class Explosion
{
public:
	unsigned int remaining_duration;
	unsigned int total_duration;

	ExplosionDat explosion_data;

	Explosion(ExplosionDat in_data)
	{
		remaining_duration = total_duration = DEFAULT_EXPLOSION_DURATION;
		explosion_data = in_data;
	}

	bool tick()
	{
		remaining_duration--;
		return (!remaining_duration); //if the 'duration' is zero, return true, signaling a fade
	}
};



struct RandMapSettings
{
	unsigned int H, W, start_spots;
	float density;

	RandMapSettings()
	{	H = 64;
		W = 48;
		start_spots = 4;
		density = 0.8f;}

	RandMapSettings(	unsigned int in_H, 
						unsigned int in_W, 
						unsigned int in_s_sp, 
						float in_d)
	{	H = in_H;
		W = in_W;
		start_spots = in_s_sp;
		density = in_d;}
};

struct GameSettings
{
	int n_bombers;
	vector<string> vm_bomber_files;
	vector<int> bomber_teams;

	char * map_name; //map_name = 0 ==> random map
	RandMapSettings rmap_settings;

	ScoringProperties vict_s_prop;
	ActiveProperties vict_a_prop;
};

/*
TO DO:
- add power up drops to tile.explode() ----------------- DONE
- add a random number generator or get one from wx ----- DONE
- add real powerups -	graphics in 136 ----------------------------------------- more bitmaps
						indexing taken care of in stage (make sure) ------------- DONE
						pickup in engine in bomber code, create_powerup(coords) - DONE
							(also 'use' in engine-bomber code) ------------------ DONE
		*for now: speed, power, kicking, +bombs(?), detonation - add more later - DONE
- implement maps and/or random maps - just a stage function - random is ez ------ DONE
- add the widgets to tweak settings in 136 - zoom would be nice
- spiff up the bitmaps
- improve fresh_id system ------------------------------------------------------- DONE
*/









struct VMBomber
{
	unsigned int id;
	unsigned int team_id;
	Coordinates coords;
};

struct VMBomb
{
	bool is_callers;
	Coordinates coords;
};

struct VMPowerUp
{
	unsigned int type;
	Coordinates coords;
};

struct VMTile
{
	int type;
};

struct VMBomberStatus
{
	Coordinates position; //coords
	int player_id;    //id
	int team_id;      //team_id
	int move_rate;    //mstate.move_steps
	bool has_kick;    //prop.kicking
	bool has_detonate; //prop.detonation
	int radius; //bomb_prop.power
	int max_bombs; //prop.max_bombs
	int deployed_bombs; //nbombs
};

struct VMInput
{
	vector<VMBomber> vm_bombers;
	vector<VMBomb> vm_bombs;
	vector<VMPowerUp> vm_powerups;
	unsigned int H,W;
	VMTile * vm_tiles; //access is based on i*H + j
	VMBomberStatus vm_bomber_status;

	VMInput()
	{vm_tiles = 0;};
};

#endif
